using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.Entities;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.DataLayer
{
    public partial class CompanyScreeningRepository
    {
        private readonly MongoClient _client;
        private readonly IMongoDatabase _clientMongoDb;
        private static Lazy<bool> EnsureBsonMap = new Lazy<bool>(InitMap, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);

        private static bool InitMap()
        {
            if (!BsonClassMap.IsClassMapRegistered(typeof(BaseCriteria)))
            {
                BsonClassMap.RegisterClassMap<BaseCriteria>(cm =>
                {
                    cm.MapIdProperty(c => c.Id);
                    cm.MapProperty(c => c.Name);
                    cm.MapProperty(c => c.CriteriaType);
                    cm.MapProperty(c => c.FilterId);
                    cm.MapProperty(c => c.CriteriaLevel2Id);
                    cm.MapProperty(c => c.CompaniesIdsResult);
                    cm.MapProperty(c => c.CriteriaEntityField);
                    cm.MapProperty(c => c.Order);
                    cm.MapProperty(c => c.TemplateCriteriaId);
                    cm.MapProperty(c => c.Category);
                    cm.MapProperty(c => c.HasOwnValue);
                    cm.MapProperty(c => c.HasFullRangeChart);
                    cm.MapProperty(c=>c.isActiveCategory);
                });
            }

            if (!BsonClassMap.IsClassMapRegistered(typeof(BaseUserLog)))
            {
                // map base class to ignore extra elements of derived classes
                BsonClassMap.RegisterClassMap<BaseUserLog>(cm =>
                {
                    cm.MapIdProperty(c => c.Id);
                    cm.MapProperty(c => c.UserId);
                    cm.MapProperty(c => c.Created);
                    cm.MapProperty(c => c.Type);
                    cm.SetIgnoreExtraElements(true); 
                });
            }
      
            return true;
        }

        public static ICompanyScreeningRepository GetCompanyScreeningRepository(string connectionString)
        {
            if (!EnsureBsonMap.Value)
            {
                throw new Exception("CompanyScreeningRepository : bson map not initializeed");
            }

            return new CompanyScreeningRepository(connectionString);
        }

        private CompanyScreeningRepository(string connectionString)
        {
            var dbConnectionString = MongoUrl.Create(connectionString);

            if (string.IsNullOrWhiteSpace(dbConnectionString.DatabaseName))
            {
                throw new ArgumentException("No database specified in the connection string!");
            }

            _client = new MongoClient(dbConnectionString);
            _clientMongoDb = _client.GetDatabase(dbConnectionString.DatabaseName);
        }

        public List<CriteriaLevel1> ListCriteriaLevelOne()
        {
            return _clientMongoDb.GetCollection<CriteriaLevel1>(typeof(CriteriaLevel1).Name).AsQueryable().ToList();
        }

        public List<CriteriaLevel2> ListCriteriaLevelTwo(Guid? criteriaLevel1Id)
        {
            var query = _clientMongoDb.GetCollection<CriteriaLevel2>(typeof(CriteriaLevel2).Name).AsQueryable();
            if (criteriaLevel1Id.HasValue && criteriaLevel1Id.Value != Guid.Empty)
                query = query.Where(c => c.CriteriaLevel1Id == criteriaLevel1Id.Value);

            return query.ToList();
        }

        public List<BaseCriteria> ListBaseCriteria(Guid? criteriaLevel2Id)
        {
            var query = _clientMongoDb.GetCollection<BaseCriteria>(typeof(CriteriaLevel3).Name).AsQueryable();
            if (criteriaLevel2Id.HasValue && criteriaLevel2Id.Value != Guid.Empty)
                query = query.Where(c => c.CriteriaLevel2Id == criteriaLevel2Id.Value);

            return query.ToList();
        }

        public List<CriteriaLevel2> ListFeatureUniversesByCriteriaLevelTwoId(List<Guid> criteriaLevel2Id)
        {
            return _clientMongoDb.GetCollection<CriteriaLevel2>(typeof(CriteriaLevel2).Name)
                .Find(Builders<CriteriaLevel2>.Filter.In(p => p.Id, criteriaLevel2Id))
                .Project<CriteriaLevel2>(Builders<CriteriaLevel2>.Projection
                    .Include(p => p.Id)
                    .Include(p => p.UniverseFeature))
                .ToList();
        }

        #region FilterCriteria

        public FilterCriteria GetFilterCriteria(Guid filterId, Guid templateCriteriaId)
        {
            return _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name).AsQueryable()
                .FirstOrDefault(x => x.FilterId == filterId && x.TemplateCriteriaId == templateCriteriaId);
        }

        public int GetLastCountForFilterCriteria(Guid filterId)
        {
            var element = _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name).AsQueryable()
                .Where(x => x.FilterId == filterId)
                .OrderByDescending(x => x.Order)
                .FirstOrDefault();

            if (element == null)
                return 1;

            return element.Order + 1;
        }

        public void AddFilterCriteria(FilterCriteria filterCriteria)
        {
            _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name).InsertOne(filterCriteria);
        }

        public void UpdateFilterCriteria(FilterCriteria filterCriteria)
        {
            _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name).FindOneAndReplace(
                Builders<FilterCriteria>.Filter.Eq(x=> x.Id, filterCriteria.Id),
                filterCriteria);
        }

        public void UpdateSelectedCriteriaOrderNumber(Guid filterId, List<Guid> criteriaIds)
        {
            for (int i = 0; i < criteriaIds.Count; i++)
            {
                var criteriaId = criteriaIds[i];

                var update = Builders<BaseCriteria>.Update.Set(a => a.Order, i + 1);
                var query = Builders<BaseCriteria>.Filter.Where(c => c.FilterId == filterId && c.Id == criteriaId);

                _clientMongoDb.GetCollection<BaseCriteria>(typeof(FilterCriteria).Name).UpdateMany(query, update);
            }
        }

        #endregion

        #region TemplateCriteria

        public List<TemplateCriteria> ListTemplateCriteriaByLevel3Id(Guid? criteriaLevel3Id)
        {
            var query = _clientMongoDb.GetCollection<TemplateCriteria>(typeof(TemplateCriteria).Name)
                .AsQueryable()
                .Where(x => x.CriteriaLevel3Id != Guid.Empty);

            if (criteriaLevel3Id.HasValue && criteriaLevel3Id.Value != Guid.Empty)
                query = query.Where(c => c.CriteriaLevel3Id == criteriaLevel3Id.Value).OrderBy(a=>a.Order);

            return query.ToList();
        }

        public TemplateCriteria GetTemplateCriteriaById(Guid templateCriteriaId)
        {
            return _clientMongoDb.GetCollection<TemplateCriteria>(typeof(TemplateCriteria).Name).AsQueryable()
                .FirstOrDefault(c => c.Id == templateCriteriaId);
        }

        public List<TemplateCriteria> GetDataPointsByCriteriaLevel2Id(Guid criteriaLevel2Id)
        {
            var level3s = _clientMongoDb.GetCollection<CriteriaLevel3>(typeof(CriteriaLevel3).Name)
                .Find(c => c.CriteriaLevel2Id == criteriaLevel2Id &&
                            c.isActiveCategory)
                .Project<CriteriaLevel3>(Builders<CriteriaLevel3>.Projection.Include(x => x.Id))
                .ToList()
                .Select(x => x.Id);

            var result = _clientMongoDb.GetCollection<TemplateCriteria>(typeof(TemplateCriteria).Name)
                .Find(Builders<TemplateCriteria>.Filter.In(c => c.CriteriaLevel3Id, level3s))
                .ToList();

            return result.OrderBy(x => x.Order).ToList();
        }

        public List<TemplateCriteria> ListTemplateCriteria()
        {
            return _clientMongoDb.GetCollection<TemplateCriteria>(typeof(TemplateCriteria).Name)
                .AsQueryable()
                .Where(x => x.CriteriaLevel3Id != Guid.Empty)
                .ToList();
        }

        public Guid GetTemplateCriteriaIdByPISymbol(string piSymbol)
        {
            piSymbol = piSymbol + " ";
            var template = _clientMongoDb.GetCollection<TemplateCriteria>(typeof(TemplateCriteria).Name).AsQueryable()
                .Where(tc => tc.CriteriaType == CriteriaTypeEnum.CriteriaType4)
                .Where(tc => tc.CriteriaEntityField.Contains(piSymbol))
                .Where(tc => tc.IsScreenable)
                .FirstOrDefault();

            return template.Id;
        }

        #endregion
    }
}
